1 Exploratory Data Analysis

R is a tool designed by statisticians and as such there is an immense amount of options to perform exploratory data analysis tasks. This section will cover just a few of those methods. In the previous sections, we did our setup tasks, including loading required packages and uploading our required data.

Below, we’ll cover a few examples of functions included in base R to quickly summarize our data. We’ll also leverage package functions to create easy frequency tables to show off some of the flexibility packages afford us.

PATH <- "S:/Pricing/Data/Demand/"
DATA <- read_fst(paste0(PATH,"Closing_a_cw_20181031_by_driver_CTR_QC.fst"))

1.1 Quick EDA

Assess the dimensions of the data

dim(DATA)
[1] 172489    987

There are a few ways to visualize your data, the simplest is head() which gives you the first line of your data. Also, if your data is too large to visualize in the console, you can do view() or click the table from your environment.

head(DATA)[1:15]

# To view the head of the data in the code editor
View(head(DATA))

Mean, Min, Max and Quantile by predictor variable

summary(DATA[,c('Clo_Sale_In', 'Dri_Age_Nb', 'Veh_Age_Nb', 'Dri_Yrs_Licensed_AU_Nb')])
  Clo_Sale_In       Dri_Age_Nb      Veh_Age_Nb     Dri_Yrs_Licensed_AU_Nb
 Min.   :0.0000   Min.   :16.00   Min.   :-1.000   Min.   : 0.00         
 1st Qu.:0.0000   1st Qu.:27.00   1st Qu.: 1.000   1st Qu.: 6.00         
 Median :0.0000   Median :35.00   Median : 4.000   Median :13.00         
 Mean   :0.2658   Mean   :37.75   Mean   : 5.173   Mean   :16.81         
 3rd Qu.:1.0000   3rd Qu.:46.00   3rd Qu.: 8.000   3rd Qu.:25.00         
 Max.   :1.0000   Max.   :99.00   Max.   :96.000   Max.   :75.00         

Two or more way frequency table

DATA %>%
  group_by(Clo_Sale_In,Dri_Gender_Cd,Dri_Marital_Status_In,Clt_Insurer_Tx) %>%
  summarise(n = n())

How to quickly spot data errors?

A common known data issue is Multiline discount and the Client home main location variable; If the indicator is showing a multiline discount then we should be able to see a client main location for that client.

Here is a quick way to spot this data issue:

DATA %>% 
  filter(Quo_Type_Tx == "Inbound") %>%
  group_by(Rat_DISML_In,Clt_Home_Main_Location_Cd) %>%
  summarise(n = n())

Before diving into an example using a real work situation, let’s cover a note on memory usage.

1.2 Memory usage in R

It is important to understand what is happening in the background when using R and users should be aware of how much memory is being used. Basically, R is a memory based software where all the data is stored on memory and not on the hard drive like other softwares. This means it’s important to manage your memory usage in order to avoid running into memory limitations.

mem_used()
953 MB
object_size(DATA)
863 MB

Example: I will copy data object to another object called x just for illustration purposes. mem_change function determines the change in memory from running the code. By creating a new data (x) we increase the memory used by 376 B.

mem_change(x <- DATA)
376 B

Then, by removing the data x, we get 320 B of memory back.

mem_change(rm(x))
320 B

2 Example

Let’s say the manager of your team wants you to look at the profitability on new businesses (Quotes that closed).

2.1 Scoring the LC Model With SOS

First thing we will do is to score our loss cost model on our data. Currently, to score LC models for Quebec in R - we use the RUO package.

2.1.1 Load RUO - Read Model from Excel

The first step of this process is to load the file 00 - Functions which is the data preparation part specific to Quebec PPA data. Then, we load our different loss cost models (by coverage) that we have in excel. 1 & 2 in the model names refer to “NIC” and “NonNIC” portions of the loss cost.

2.1.2 Apply Data modifications before scoring

library(RUO)
source("S:/Pricing/Innovation/R Ressources/R - Training 2.0/Training/00 - Functions.R")

PATH_RUO <- 'S:/Pricing/Innovation/R Ressources/R - Training 2.0/Training/RUO_LC_QC_PPA_V2.0/'

LC_MODEL <- EXCEL.to.R(paste0(PATH_RUO,"LC_QC_PPA_V2.0_201706.xls"))

# These variable attribute will be used later. They are parameters needed for RUO
MODEL_NAMES <- c("TPL1","TPL2","COLL1","COLL2","COMP1","COMP2")
Uniq_Keys <- c("Clt_Insurer_No", "Clt_Account_No", "Pol_Policy_No", "Veh_Id_No", "Dri_Id_No")

Prior to scoring, some modifications on the data need to be made. This in-house function makes sure the data and model match up properly. You can open the function to see what changes is been made to the data.

DATA <- data_modifications(DATA = DATA)

2.1.3 Score Data - Apply Model To Data

Final step is to use the APPLY.MOD or APPLY.MOD.lm function which will allow you to specify your link function, the name of your model and the name of your data to properly score it.

APPLY.MOD() - returns model predictions as well as the banded variables and relativities of each variable and is generally used for validation purposes. The idea here is to show the relativity variables created.

DATA <- as.data.frame(DATA)

DATA_RELS <- APPLY.MOD(RMOD = LC_MODEL,
                       link_Fnc = "log",
                       Uniq_Keys = Uniq_Keys,
                       DATA = DATA,
                       Split = F,
                       MODEL_NAME = MODEL_NAMES)

Here is to show the relativity variables created. You could do the same exercice by replacing _Rel with _Bnd to see the banded variables.

head(names(DATA_RELS)[grep('_Rel', names(DATA_RELS))])
[1] "TPL1_Clt_Credit_Score_No_Rel"     "TPL1_Clt_Previous_Ins_In_Rel"    
[3] "TPL1_Clt_Yrs_Client_Since_Nb_Rel" "TPL1_Dri_Age_Nb_Rel"             
[5] "TPL1_Dri_Gender_Cd_Rel"           "TPL1_Dri_Type_Cd_Rel"            

APPLY.MOD.lm() - can return the final score only, but also the relativities if we want (and it’s much faster than APPLY.MOD because it can run in parallel using C++ coding)

DATA_All <- APPLY.MOD.lm(RMOD = LC_MODEL,
                         link_Fnc = "log",
                         Uniq_Keys = Uniq_Keys,
                         DATA = DATA,
                         MODEL_NAME = MODEL_NAMES,
                         KEEP_DATA = T,
                         KEEP_RELS = F)

# Calculate the total LC
DATA_Scored <- DATA_All %>% 
  mutate(TPL_PRED = round(TPL1_PRED*TPL2_PRED),
         COLL_PRED = round(COLL1_PRED*COLL2_PRED),
         COMP_PRED = round(COMP1_PRED*COMP2_PRED),
         LC_TOTAL = TPL_PRED + COLL_PRED + COMP_PRED)

The Scoring Object System initiative aims to replace Radar with R scripts. The RUO package is the main component of this project, which takes Radar exports such as the Bandings and Multivariate tables and converts them into an R-usable format. We then created scripts to score this new object (the Scoring Object) in R. The above block of code gives an example of this in action.

2.2 Rebasing

Let’s now use our scored object to perform a rebase. We will also use this opportunity to break down the more common functions used in the dplyr package.

We must create a data frame with indicated loss cost by vehicle and by company first. Usually, we would do the exercice also by coverage, but for simplicity, we rebase only by Insurer in this exercice.

DATA_Indicated = data.frame( Clt_Insurer_Tx         = c("PIC", "SN",  "TDHA"),
                             Indicated_Total_Avg_Am = c(1160, 1112, 860))

We still need the loss costs to be equal at the aggregate level. To put the current model on the same average level as indicated, we must calculate the rebasing factor

The code below can seem intimidating so we will give a more in depth breakdown of some of the main functions used with dplyr. If you are familiar with SAS you will recognize a lot of these functions

select: A function used to choose the variables you need

group_by: Grouping by allows for the user to group a long a list of characteristics. Frequently used with either summarise or previously seen slice(1).

summarise: Allows you to calculate aggregated variables for a group such as the average, sum, standard deviation and others

filter: Allows you to only take observation that match a certain criteria, thus allowing you to subset your data

left_join: Allows you to join two tables together joined by a variable (of multiple ones), which acts as a unique key

mutate: Allows you to add more variables to your tables. Mutate creates new variables, you can compare it with the data; set; step in SAS.

We’ll include a more detailed breakdown of calculating the rebase factor, and we’ll include the full step in one calculation underneath it.

The first step involves a calculation of the Loss Cost for the respective vehicles. For that we sum our loss cost by vehicle using the group_by function.

Rebase_Factor_1 <- DATA_Scored %>%
  group_by(Clt_Insurer_Tx,
           Clt_Account_No, 
           Pol_Policy_No, 
           Veh_Id_No) %>%
  summarise(LC_Veh_Total = sum(LC_TOTAL))

We then group by our company to find the average loss cost for each company. na.rm is an option which exclude the NA values for the mean calculation (not from the data). If we have a NA value and we don’t add this option, mean will return NA.

Rebase_Factor_2 <- Rebase_Factor_1 %>%
  group_by(Clt_Insurer_Tx) %>%
  summarise(Exposure = sum(n()),
            LC_Veh_Total_Avg = mean(LC_Veh_Total, na.rm = TRUE)) 

Finally we join these loss cost vehicle averages to the indicated total amount to calculate the rebasing factor. Because we should not have TDGI in our data (only one exposure), we will remove it

Rebase_Factor_3 <- Rebase_Factor_2 %>%
  left_join(DATA_Indicated) %>%
  filter(Clt_Insurer_Tx != "TDGI") %>%
  mutate(Rebase_Factor = Indicated_Total_Avg_Am/LC_Veh_Total_Avg)

And this can all be combined into this one code below

Rebase_Factor <- DATA_Scored %>%
  group_by(Clt_Insurer_Tx,
           Clt_Account_No, 
           Pol_Policy_No, 
           Veh_Id_No) %>%
  summarise(LC_Veh_Total = sum(LC_TOTAL)) %>%
  group_by(Clt_Insurer_Tx) %>%
  summarise(Exposure = sum(n()),
            LC_Veh_Total_Avg = mean(LC_Veh_Total, na.rm=TRUE)) %>%
  left_join(DATA_Indicated) %>%
  filter(Clt_Insurer_Tx != "TDGI") %>%
  mutate(Rebase_Factor = Indicated_Total_Avg_Am/LC_Veh_Total_Avg)

Once the rebasing factor is calculated, we can rebase the scored total loss cost to have the same average indicated loss cost for each company

Apply rebasing factor

DATA_Scored <- DATA_Scored %>%
  left_join(Rebase_Factor) %>%
  mutate(LC_TOTAL_Rebased = LC_TOTAL * Rebase_Factor)

2.3 Profitability Analysis (A Lesson in Data Manipulation)

Before moving on to plotting, let’s conclude our example with a profitability analysis. First looking at expected profitability of the quotes that closed by company


AGG.VEH <- DATA_Scored %>%
  group_by(Clt_Insurer_Tx,
           Clt_Account_No, 
           Pol_Policy_No, 
           Veh_Id_No) %>%
  summarise(LC_Veh_Total = sum(LC_TOTAL))

AGG.INSURER <- DATA_Scored %>% 
  left_join(AGG.VEH) %>%
  filter(Clo_Sale_In == 1 & Dri_Type_Cd == 'P') %>% # To have the data by vehicle
  select(LC_Veh_Total,
         Prm_Trm_Veh_Tot_Am,
         Clt_Insurer_Tx) %>% 
  group_by(Clt_Insurer_Tx) %>% 
  summarise(Prm_Trm_Veh_Mean = mean(Prm_Trm_Veh_Tot_Am),
            LR_Veh_Mean  = sum(LC_Veh_Total, na.rm = TRUE)/sum(Prm_Trm_Veh_Tot_Am),
            Xpo_Veh_Nb   = sum(n()))

Then we can calculate the expected profitablity of the quotes that closed by Marital Status and Driver Age

AGG.DRI_AGE <- DATA_Scored %>% 
  left_join(AGG.VEH) %>%
  filter(Clo_Sale_In == 1 & Dri_Type_Cd == 'P') %>% # To have the data by vehicle
  select(LC_Veh_Total,
         Prm_Trm_Veh_Tot_Am,
         Dri_Age_Nb,
         Dri_Marital_Status_In) %>% 
  group_by(Dri_Age_Nb,
           Dri_Marital_Status_In) %>% 
  summarise(Prm_Trm_Veh_Mean = mean(Prm_Trm_Veh_Tot_Am),
            LR_Veh_Mean  = sum(LC_Veh_Total, na.rm = TRUE)/sum(Prm_Trm_Veh_Tot_Am),
            Xpo_Veh_Nb   = sum(n()))

And finally, we can calculate the expected profitablity of the quotes that closed by the Gender of the principal driver and his number of years licensed

AGG.YRS_LIC <- DATA_Scored %>% 
  left_join(AGG.VEH) %>%
  filter(Clo_Sale_In == 1 & Dri_Type_Cd == 'P') %>% # To have the data by vehicle
  select(LC_Veh_Total,
         Prm_Trm_Veh_Tot_Am,
         Dri_Gender_Cd,
         Dri_Yrs_Licensed_AU_Nb) %>% 
  group_by(Dri_Gender_Cd,
           Dri_Yrs_Licensed_AU_Nb) %>% 
  summarise(Prm_Trm_Veh_Mean = mean(Prm_Trm_Veh_Tot_Am),
            LR_Veh_Mean  = sum(LC_Veh_Total, na.rm = TRUE)/sum(Prm_Trm_Veh_Tot_Am),
            Xpo_Veh_Nb   = sum(n()))

3 Plotting Results

Finally, we will cover R’s plotting functionality before moving onto our functions lesson. R has two main packages which offer extremely powerful options for plotting: ggplot2 and plotly. Each have their own strengths and weaknesses, but ultimately they’re both powerful enough that your decision can come down to personal preference. In this lesson, we’ll use plotly, but the reader is encouraged to look at ggplot2 if they are interested.

Just as an fyi, ggplot2 does not allow to have 2 y-axis. In that case, the package plotly is preferred

3.1 Example: Visualization (profitability analysis)

Loss Ratio by Insurer

The goal of this graph is to be able to have both a line graph and a bar graph within the same plot. This allows user to visualize multiple aspects of the data at the same time. In this case the bar graph will represent exposure for each insurer while the line graph overlaying it will represent the average loss ratio.

For building plots we can use a similar step by step approach as dplyr

P_LR_Insurer <- plot_ly(data = AGG.INSURER) %>%
  add_lines(
    x = ~Clt_Insurer_Tx,
    y = ~LR_Veh_Mean,
    name = "Loss Ratio",
    yaxis = "y"
  )

Allows more and more graphic items to be added on. All that needs to be done is to know the correct grammar of the programming language.

P_LR_Insurer2 <- plot_ly(data = AGG.INSURER) %>%
  add_lines(x = ~Clt_Insurer_Tx, y = ~LR_Veh_Mean,
            name = "Loss Ratio",
            yaxis = "y"
  ) %>%
  add_bars(x = ~Clt_Insurer_Tx, y = ~Xpo_Veh_Nb, 
           name = "Exposure",
           yaxis = "y2"
  ) %>%
  layout(
    title = "Loss Ratio by Insurer",
    xaxis = list(title = "Insurer"),
    yaxis = list(title = "Loss Ratio", side = "left", overlaying = "y2"),
    yaxis2 = list(title = "Exposure", side = "right")
  )

Rescale y-axis

P_LR_Insurer3 <- P_LR_Insurer2 %>%
  layout(
    yaxis = list(
      range = c(0, 2)
    )
  )

Marital x Driver Age

AGG.DRI_AGE <- ungroup(AGG.DRI_AGE)


P_LR_Marital_Age <- plot_ly(data = AGG.DRI_AGE, color =~Dri_Marital_Status_In, colors = c("red","darkgreen")) %>%
  add_bars(x=~Dri_Age_Nb, y=~Xpo_Veh_Nb, yaxis="y2", opacity = .5)%>%
  add_lines(x=~Dri_Age_Nb, y=~LR_Veh_Mean) %>%
  layout(
    barmode="stack",
    title = "Loss Ratio by Driver Age and Marital Status",
    xaxis = list(title = "Driver Age", range = c(16, 70)),
    yaxis = list(title = "Loss Ratio", side = "left", overlaying = "y2"),
    yaxis2 = list(title = "Exposure", side = "right")
  )

Gender x Yrs Licensed

P_LR_Gender_YL <- plot_ly(data = AGG.YRS_LIC, color =~Dri_Gender_Cd, colors = c("red","darkgreen")) %>%
  add_bars(x=~Dri_Yrs_Licensed_AU_Nb, y=~Xpo_Veh_Nb, yaxis="y2")%>%
  add_lines(x=~Dri_Yrs_Licensed_AU_Nb, y=~LR_Veh_Mean) %>%
  layout(
    barmode="group",
    title = "Loss Ratio by Gender and Years Licensed",
    xaxis = list(title = "Years Licensed", range = c(-1, 60)),
    yaxis = list(title = "Loss Ratio", side = "left", overlaying = "y2"),
    yaxis2 = list(title = "Exposure", side = "right")
  )

We can clearly see that graphs of the same type have a very similar code structure allowing users to create new graphs easily

We can now save these graphs to html files that can be used for later analysis. Do not forget to change the path for your own directory.

path <-"S:/Pricing/Innovation/R Ressources/R - Training 2.0/Training/"
saveWidget(P_LR_Insurer,file.path(normalizePath(dirname(paste0(path, "Clt_Insurer_Tx", ".html"))),basename(paste0(path, "Clt_Insurer_Tx", ".html"))))

saveWidget(P_LR_Gender_YL,file.path(normalizePath(dirname(paste0(path, "Gender_x_YL", ".html"))),basename(paste0(path, "Gender_x_YL", ".html"))))
LS0tDQp0aXRsZTogJ1IgVHJhaW5pbmc6IDAyIC0gRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcycNCmF1dGhvcjogIlByaWNpbmcgSW5ub3ZhdGlvbiBUZWFtIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdGhlbWU6IGZsYXRseQ0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZWNobyA9IEZBTFNFfQ0KbGlicmFyeShmc3QpDQpsaWJyYXJ5KHByeXIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShSVU8pDQpsaWJyYXJ5KHBsb3RseSkNCmxpYnJhcnkoaHRtbHdpZGdldHMpDQprbml0cjo6b3B0c19jaHVuayRzZXQod2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGKQ0KYGBgDQoNCiMgRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcw0KDQpSIGlzIGEgdG9vbCBkZXNpZ25lZCBieSBzdGF0aXN0aWNpYW5zIGFuZCBhcyBzdWNoIHRoZXJlIGlzIGFuIGltbWVuc2UgYW1vdW50IG9mIG9wdGlvbnMgdG8gcGVyZm9ybSBleHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzIHRhc2tzLiBUaGlzIHNlY3Rpb24gd2lsbCBjb3ZlciBqdXN0IGEgZmV3IG9mIHRob3NlIG1ldGhvZHMuIEluIHRoZSBwcmV2aW91cyBzZWN0aW9ucywgd2UgZGlkIG91ciBzZXR1cCB0YXNrcywgaW5jbHVkaW5nIGxvYWRpbmcgcmVxdWlyZWQgcGFja2FnZXMgYW5kIHVwbG9hZGluZyBvdXIgcmVxdWlyZWQgZGF0YS4NCg0KDQpCZWxvdywgd2UnbGwgY292ZXIgYSBmZXcgZXhhbXBsZXMgb2YgZnVuY3Rpb25zIGluY2x1ZGVkIGluIGJhc2UgUiB0byBxdWlja2x5IHN1bW1hcml6ZSBvdXIgZGF0YS4gV2UnbGwgYWxzbyBsZXZlcmFnZSBwYWNrYWdlIGZ1bmN0aW9ucyB0byBjcmVhdGUgZWFzeSBmcmVxdWVuY3kgdGFibGVzIHRvIHNob3cgb2ZmIHNvbWUgb2YgdGhlIGZsZXhpYmlsaXR5IHBhY2thZ2VzIGFmZm9yZCB1cy4NCg0KYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgZXZhbCA9IEZBTFNFfQ0KUEFUSCA8LSAiUzovUHJpY2luZy9EYXRhL0RlbWFuZC8iDQpEQVRBIDwtIHJlYWRfZnN0KHBhc3RlMChQQVRILCJDbG9zaW5nX2FfY3dfMjAxODEwMzFfYnlfZHJpdmVyX0NUUl9RQy5mc3QiKSkNCmBgYA0KDQojIyBRdWljayBFREENCg0KQXNzZXNzIHRoZSBkaW1lbnNpb25zIG9mIHRoZSBkYXRhDQoNCmBgYHtyfQ0KZGltKERBVEEpDQpgYGANCg0KVGhlcmUgYXJlIGEgZmV3IHdheXMgdG8gdmlzdWFsaXplIHlvdXIgZGF0YSwgdGhlIHNpbXBsZXN0IGlzIGhlYWQoKSB3aGljaCBnaXZlcyB5b3UgdGhlIGZpcnN0IGxpbmUgb2YgeW91ciBkYXRhLiBBbHNvLCBpZiB5b3VyIGRhdGEgaXMgdG9vIGxhcmdlIHRvIHZpc3VhbGl6ZSBpbiB0aGUgY29uc29sZSwgeW91IGNhbiBkbyB2aWV3KCkgb3IgY2xpY2sgdGhlIHRhYmxlIGZyb20geW91ciBlbnZpcm9ubWVudC4gDQoNCmBgYHtyLCBlY2hvID0gVFJVRX0NCmhlYWQoREFUQSlbMToxNV0NCg0KIyBUbyB2aWV3IHRoZSBoZWFkIG9mIHRoZSBkYXRhIGluIHRoZSBjb2RlIGVkaXRvcg0KVmlldyhoZWFkKERBVEEpKQ0KYGBgDQoNCmBgYHtyLCBlY2hvID0gRkFMU0V9DQpoZWFkKERBVEEpWzE6MTVdDQoNCiMgVG8gdmlldyB0aGUgaGVhZCBvZiB0aGUgZGF0YSBpbiB0aGUgY29kZSBlZGl0b3INClZpZXcoaGVhZChEQVRBKSkNCmBgYA0KDQpNZWFuLCBNaW4sIE1heCBhbmQgUXVhbnRpbGUgYnkgcHJlZGljdG9yIHZhcmlhYmxlDQpgYGB7cn0NCnN1bW1hcnkoREFUQVssYygnQ2xvX1NhbGVfSW4nLCAnRHJpX0FnZV9OYicsICdWZWhfQWdlX05iJywgJ0RyaV9ZcnNfTGljZW5zZWRfQVVfTmInKV0pDQpgYGANCg0KVHdvIG9yIG1vcmUgd2F5IGZyZXF1ZW5jeSB0YWJsZQ0KDQpgYGB7ciBlY2hvPVRSVUV9DQpEQVRBICU+JQ0KICBncm91cF9ieShDbG9fU2FsZV9JbixEcmlfR2VuZGVyX0NkLERyaV9NYXJpdGFsX1N0YXR1c19JbixDbHRfSW5zdXJlcl9UeCkgJT4lDQogIHN1bW1hcmlzZShuID0gbigpKQ0KYGBgDQoNCmBgYHtyfQ0KREFUQSAlPiUNCiAgZ3JvdXBfYnkoQ2xvX1NhbGVfSW4sRHJpX0dlbmRlcl9DZCxEcmlfTWFyaXRhbF9TdGF0dXNfSW4sQ2x0X0luc3VyZXJfVHgpICU+JQ0KICBzdW1tYXJpc2UobiA9IG4oKSkNCmBgYA0KDQpIb3cgdG8gcXVpY2tseSBzcG90IGRhdGEgZXJyb3JzPw0KDQpBIGNvbW1vbiBrbm93biBkYXRhIGlzc3VlIGlzIE11bHRpbGluZSBkaXNjb3VudCBhbmQgdGhlIENsaWVudCBob21lIG1haW4gbG9jYXRpb24gdmFyaWFibGU7IElmIHRoZSBpbmRpY2F0b3IgaXMgc2hvd2luZyBhIG11bHRpbGluZSBkaXNjb3VudCB0aGVuIHdlIHNob3VsZCBiZSBhYmxlIHRvIHNlZSBhIGNsaWVudCBtYWluIGxvY2F0aW9uIGZvciB0aGF0IGNsaWVudC4gDQoNCkhlcmUgaXMgYSBxdWljayB3YXkgdG8gc3BvdCB0aGlzIGRhdGEgaXNzdWU6DQoNCmBgYHtyLCBlY2hvID0gVFJVRX0NCkRBVEEgJT4lIA0KICBmaWx0ZXIoUXVvX1R5cGVfVHggPT0gIkluYm91bmQiKSAlPiUNCiAgZ3JvdXBfYnkoUmF0X0RJU01MX0luLENsdF9Ib21lX01haW5fTG9jYXRpb25fQ2QpICU+JQ0KICBzdW1tYXJpc2UobiA9IG4oKSkNCg0KYGBgDQoNCmBgYHtyLCBlY2hvID0gRkFMU0V9DQpEQVRBICU+JQ0KICBmaWx0ZXIoUXVvX1R5cGVfVHggPT0gIkluYm91bmQiKSAlPiUNCiAgZ3JvdXBfYnkoUmF0X0RJU01MX0luLENsdF9Ib21lX01haW5fTG9jYXRpb25fQ2QpICU+JQ0KICBzdW1tYXJpc2UobiA9IG4oKSkNCg0KYGBgDQoNCkJlZm9yZSBkaXZpbmcgaW50byBhbiBleGFtcGxlIHVzaW5nIGEgcmVhbCB3b3JrIHNpdHVhdGlvbiwgbGV0J3MgY292ZXIgYSBub3RlIG9uIG1lbW9yeSB1c2FnZS4NCg0KIyMgTWVtb3J5IHVzYWdlIGluIFINCg0KSXQgaXMgaW1wb3J0YW50IHRvIHVuZGVyc3RhbmQgd2hhdCBpcyBoYXBwZW5pbmcgaW4gdGhlIGJhY2tncm91bmQgd2hlbiB1c2luZyBSIGFuZCB1c2VycyBzaG91bGQgYmUgYXdhcmUgb2YgaG93IG11Y2ggbWVtb3J5IGlzIGJlaW5nIHVzZWQuIEJhc2ljYWxseSwgUiBpcyBhIG1lbW9yeSBiYXNlZCBzb2Z0d2FyZSB3aGVyZSBhbGwgdGhlIGRhdGEgaXMgc3RvcmVkIG9uIG1lbW9yeSBhbmQgbm90IG9uIHRoZSBoYXJkIGRyaXZlIGxpa2Ugb3RoZXIgc29mdHdhcmVzLiBUaGlzIG1lYW5zIGl0J3MgaW1wb3J0YW50IHRvIG1hbmFnZSB5b3VyIG1lbW9yeSB1c2FnZSBpbiBvcmRlciB0byBhdm9pZCBydW5uaW5nIGludG8gbWVtb3J5IGxpbWl0YXRpb25zLg0KDQpgYGB7ciBtZW1vcnkgdXNhZ2V9DQptZW1fdXNlZCgpDQpgYGANCg0KDQpgYGB7cn0NCm9iamVjdF9zaXplKERBVEEpDQpgYGANCg0KDQpFeGFtcGxlOiBJIHdpbGwgY29weSBkYXRhIG9iamVjdCB0byBhbm90aGVyIG9iamVjdCBjYWxsZWQgeCBqdXN0IGZvciBpbGx1c3RyYXRpb24gcHVycG9zZXMuIG1lbV9jaGFuZ2UgZnVuY3Rpb24gZGV0ZXJtaW5lcyB0aGUgY2hhbmdlIGluIG1lbW9yeSBmcm9tIHJ1bm5pbmcgdGhlIGNvZGUuIEJ5IGNyZWF0aW5nIGEgbmV3IGRhdGEgKHgpIHdlIGluY3JlYXNlIHRoZSBtZW1vcnkgdXNlZCBieSAzNzYgQi4NCg0KYGBge3J9DQptZW1fY2hhbmdlKHggPC0gREFUQSkNCmBgYA0KDQpUaGVuLCBieSByZW1vdmluZyB0aGUgZGF0YSB4LCB3ZSBnZXQgMzIwIEIgb2YgbWVtb3J5IGJhY2suDQoNCmBgYHtyfQ0KbWVtX2NoYW5nZShybSh4KSkNCmBgYA0KDQojIEV4YW1wbGUNCg0KTGV0J3Mgc2F5IHRoZSBtYW5hZ2VyIG9mIHlvdXIgdGVhbSB3YW50cyB5b3UgdG8gbG9vayBhdCB0aGUgcHJvZml0YWJpbGl0eSBvbiBuZXcgYnVzaW5lc3NlcyAoUXVvdGVzIHRoYXQgY2xvc2VkKS4NCg0KDQojIyBTY29yaW5nIHRoZSBMQyBNb2RlbCBXaXRoIFNPUw0KDQpGaXJzdCB0aGluZyB3ZSB3aWxsIGRvIGlzIHRvIHNjb3JlIG91ciBsb3NzIGNvc3QgbW9kZWwgb24gb3VyIGRhdGEuDQpDdXJyZW50bHksIHRvIHNjb3JlIExDIG1vZGVscyBmb3IgUXVlYmVjIGluIFIgLSB3ZSB1c2UgdGhlIFJVTyBwYWNrYWdlLg0KDQojIyMgTG9hZCBSVU8gLSBSZWFkIE1vZGVsIGZyb20gRXhjZWwNCg0KVGhlIGZpcnN0IHN0ZXAgb2YgdGhpcyBwcm9jZXNzIGlzIHRvIGxvYWQgdGhlIGZpbGUgMDAgLSBGdW5jdGlvbnMgd2hpY2ggaXMgdGhlIGRhdGEgcHJlcGFyYXRpb24gcGFydCBzcGVjaWZpYyB0byBRdWViZWMgUFBBIGRhdGEuDQpUaGVuLCB3ZSBsb2FkIG91ciBkaWZmZXJlbnQgbG9zcyBjb3N0IG1vZGVscyAoYnkgY292ZXJhZ2UpIHRoYXQgd2UgaGF2ZSBpbiBleGNlbC4NCjEgJiAyIGluIHRoZSBtb2RlbCBuYW1lcyByZWZlciB0byAiTklDIiBhbmQgIk5vbk5JQyIgcG9ydGlvbnMgb2YgdGhlIGxvc3MgY29zdC4NCiANCiMjIyBBcHBseSBEYXRhIG1vZGlmaWNhdGlvbnMgYmVmb3JlIHNjb3JpbmcgDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGVjaG8gPSBUUlVFfQ0KbGlicmFyeShSVU8pDQpzb3VyY2UoIlM6L1ByaWNpbmcvSW5ub3ZhdGlvbi9SIFJlc3NvdXJjZXMvUiAtIFRyYWluaW5nIDIuMC9UcmFpbmluZy8wMCAtIEZ1bmN0aW9ucy5SIikNCg0KUEFUSF9SVU8gPC0gJ1M6L1ByaWNpbmcvSW5ub3ZhdGlvbi9SIFJlc3NvdXJjZXMvUiAtIFRyYWluaW5nIDIuMC9UcmFpbmluZy9SVU9fTENfUUNfUFBBX1YyLjAvJw0KDQpMQ19NT0RFTCA8LSBFWENFTC50by5SKHBhc3RlMChQQVRIX1JVTywiTENfUUNfUFBBX1YyLjBfMjAxNzA2LnhscyIpKQ0KDQojIFRoZXNlIHZhcmlhYmxlIGF0dHJpYnV0ZSB3aWxsIGJlIHVzZWQgbGF0ZXIuIFRoZXkgYXJlIHBhcmFtZXRlcnMgbmVlZGVkIGZvciBSVU8NCk1PREVMX05BTUVTIDwtIGMoIlRQTDEiLCJUUEwyIiwiQ09MTDEiLCJDT0xMMiIsIkNPTVAxIiwiQ09NUDIiKQ0KVW5pcV9LZXlzIDwtIGMoIkNsdF9JbnN1cmVyX05vIiwgIkNsdF9BY2NvdW50X05vIiwgIlBvbF9Qb2xpY3lfTm8iLCAiVmVoX0lkX05vIiwgIkRyaV9JZF9ObyIpDQpgYGANCg0KUHJpb3IgdG8gc2NvcmluZywgc29tZSBtb2RpZmljYXRpb25zIG9uIHRoZSBkYXRhIG5lZWQgdG8gYmUgbWFkZS4gVGhpcyBpbi1ob3VzZSBmdW5jdGlvbiBtYWtlcyBzdXJlIHRoZSBkYXRhIGFuZCBtb2RlbCBtYXRjaCB1cCBwcm9wZXJseS4gWW91IGNhbiBvcGVuIHRoZSBmdW5jdGlvbiB0byBzZWUgd2hhdCBjaGFuZ2VzIGlzIGJlZW4gbWFkZSB0byB0aGUgZGF0YS4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCkRBVEEgPC0gZGF0YV9tb2RpZmljYXRpb25zKERBVEEgPSBEQVRBKQ0KYGBgDQoNCiMjIyBTY29yZSBEYXRhIC0gQXBwbHkgTW9kZWwgVG8gRGF0YQ0KDQpGaW5hbCBzdGVwIGlzIHRvIHVzZSB0aGUgQVBQTFkuTU9EIG9yIEFQUExZLk1PRC5sbSBmdW5jdGlvbiB3aGljaCB3aWxsIGFsbG93IHlvdSB0byBzcGVjaWZ5IHlvdXIgbGluayBmdW5jdGlvbiwgdGhlIG5hbWUgb2YgeW91ciBtb2RlbCBhbmQgdGhlIG5hbWUgb2YgeW91ciBkYXRhIHRvIHByb3Blcmx5IHNjb3JlIGl0Lg0KDQoNCkFQUExZLk1PRCgpIC0gcmV0dXJucyBtb2RlbCBwcmVkaWN0aW9ucyBhcyB3ZWxsIGFzIHRoZSBiYW5kZWQgdmFyaWFibGVzIGFuZCByZWxhdGl2aXRpZXMgb2YgZWFjaCB2YXJpYWJsZSBhbmQgaXMgZ2VuZXJhbGx5IHVzZWQgZm9yIHZhbGlkYXRpb24gcHVycG9zZXMuDQpUaGUgaWRlYSBoZXJlIGlzIHRvIHNob3cgdGhlIHJlbGF0aXZpdHkgdmFyaWFibGVzIGNyZWF0ZWQuIA0KDQpgYGB7ciBtZXNzYWdlPSBGQUxTRSwgd2FybmluZz1GQUxTRX0NCkRBVEEgPC0gYXMuZGF0YS5mcmFtZShEQVRBKQ0KDQpEQVRBX1JFTFMgPC0gQVBQTFkuTU9EKFJNT0QgPSBMQ19NT0RFTCwNCiAgICAgICAgICAgICAgICAgICAgICAgbGlua19GbmMgPSAibG9nIiwNCiAgICAgICAgICAgICAgICAgICAgICAgVW5pcV9LZXlzID0gVW5pcV9LZXlzLA0KICAgICAgICAgICAgICAgICAgICAgICBEQVRBID0gREFUQSwNCiAgICAgICAgICAgICAgICAgICAgICAgU3BsaXQgPSBGLA0KICAgICAgICAgICAgICAgICAgICAgICBNT0RFTF9OQU1FID0gTU9ERUxfTkFNRVMpDQoNCmBgYA0KDQpIZXJlIGlzIHRvIHNob3cgdGhlIHJlbGF0aXZpdHkgdmFyaWFibGVzIGNyZWF0ZWQuIFlvdSBjb3VsZCBkbyB0aGUgc2FtZSBleGVyY2ljZSBieSByZXBsYWNpbmcgX1JlbCB3aXRoIF9CbmQgdG8gc2VlIHRoZSBiYW5kZWQgdmFyaWFibGVzLg0KYGBge3J9DQpoZWFkKG5hbWVzKERBVEFfUkVMUylbZ3JlcCgnX1JlbCcsIG5hbWVzKERBVEFfUkVMUykpXSkNCmBgYA0KDQpBUFBMWS5NT0QubG0oKSAtIGNhbiByZXR1cm4gdGhlIGZpbmFsIHNjb3JlIG9ubHksIGJ1dCBhbHNvIHRoZSByZWxhdGl2aXRpZXMgaWYgd2Ugd2FudCAoYW5kIGl0J3MgbXVjaCBmYXN0ZXIgdGhhbiBBUFBMWS5NT0QgYmVjYXVzZSBpdCBjYW4gcnVuIGluIHBhcmFsbGVsIHVzaW5nIEMrKyBjb2RpbmcpDQoNCmBgYHtyIG1lc3NhZ2U9IEZBTFNFLCB3YXJuaW5nPUZBTFNFLCBlY2hvID0gVFJVRX0NCkRBVEFfQWxsIDwtIEFQUExZLk1PRC5sbShSTU9EID0gTENfTU9ERUwsDQogICAgICAgICAgICAgICAgICAgICAgICAgbGlua19GbmMgPSAibG9nIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBVbmlxX0tleXMgPSBVbmlxX0tleXMsDQogICAgICAgICAgICAgICAgICAgICAgICAgREFUQSA9IERBVEEsDQogICAgICAgICAgICAgICAgICAgICAgICAgTU9ERUxfTkFNRSA9IE1PREVMX05BTUVTLA0KICAgICAgICAgICAgICAgICAgICAgICAgIEtFRVBfREFUQSA9IFQsDQogICAgICAgICAgICAgICAgICAgICAgICAgS0VFUF9SRUxTID0gRikNCg0KIyBDYWxjdWxhdGUgdGhlIHRvdGFsIExDDQpEQVRBX1Njb3JlZCA8LSBEQVRBX0FsbCAlPiUgDQogIG11dGF0ZShUUExfUFJFRCA9IHJvdW5kKFRQTDFfUFJFRCpUUEwyX1BSRUQpLA0KICAgICAgICAgQ09MTF9QUkVEID0gcm91bmQoQ09MTDFfUFJFRCpDT0xMMl9QUkVEKSwNCiAgICAgICAgIENPTVBfUFJFRCA9IHJvdW5kKENPTVAxX1BSRUQqQ09NUDJfUFJFRCksDQogICAgICAgICBMQ19UT1RBTCA9IFRQTF9QUkVEICsgQ09MTF9QUkVEICsgQ09NUF9QUkVEKQ0KYGBgDQoNClRoZSBTY29yaW5nIE9iamVjdCBTeXN0ZW0gaW5pdGlhdGl2ZSBhaW1zIHRvIHJlcGxhY2UgUmFkYXIgd2l0aCBSIHNjcmlwdHMuIFRoZSBSVU8gcGFja2FnZSBpcyB0aGUgbWFpbiBjb21wb25lbnQgb2YgdGhpcyBwcm9qZWN0LCB3aGljaCB0YWtlcyBSYWRhciBleHBvcnRzIHN1Y2ggYXMgdGhlIEJhbmRpbmdzIGFuZCBNdWx0aXZhcmlhdGUgdGFibGVzIGFuZCBjb252ZXJ0cyB0aGVtIGludG8gYW4gUi11c2FibGUgZm9ybWF0LiBXZSB0aGVuIGNyZWF0ZWQgc2NyaXB0cyB0byBzY29yZSB0aGlzIG5ldyBvYmplY3QgKHRoZSBTY29yaW5nIE9iamVjdCkgaW4gUi4gVGhlIGFib3ZlIGJsb2NrIG9mIGNvZGUgZ2l2ZXMgYW4gZXhhbXBsZSBvZiB0aGlzIGluIGFjdGlvbi4NCg0KIyMgUmViYXNpbmcNCg0KTGV0J3Mgbm93IHVzZSBvdXIgc2NvcmVkIG9iamVjdCB0byBwZXJmb3JtIGEgcmViYXNlLiBXZSB3aWxsIGFsc28gdXNlIHRoaXMgb3Bwb3J0dW5pdHkgdG8gYnJlYWsgZG93biB0aGUgbW9yZSBjb21tb24gZnVuY3Rpb25zIHVzZWQgaW4gdGhlIGRwbHlyIHBhY2thZ2UuDQoNCg0KV2UgbXVzdCBjcmVhdGUgYSBkYXRhIGZyYW1lIHdpdGggaW5kaWNhdGVkIGxvc3MgY29zdCBieSB2ZWhpY2xlIGFuZCBieSBjb21wYW55IGZpcnN0LiBVc3VhbGx5LCB3ZSB3b3VsZCBkbyB0aGUgZXhlcmNpY2UgYWxzbyBieSBjb3ZlcmFnZSwgYnV0IGZvciBzaW1wbGljaXR5LCB3ZSByZWJhc2Ugb25seSBieSBJbnN1cmVyIGluIHRoaXMgZXhlcmNpY2UuDQpgYGB7cn0NCkRBVEFfSW5kaWNhdGVkID0gZGF0YS5mcmFtZSggQ2x0X0luc3VyZXJfVHggICAgICAgICA9IGMoIlBJQyIsICJTTiIsICAiVERIQSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbmRpY2F0ZWRfVG90YWxfQXZnX0FtID0gYygxMTYwLCAxMTEyLCA4NjApKQ0KYGBgDQoNCmBgYHtyfQ0KcHJpbnQoREFUQV9JbmRpY2F0ZWQpDQpgYGANCg0KV2Ugc3RpbGwgbmVlZCB0aGUgbG9zcyBjb3N0cyB0byBiZSBlcXVhbCBhdCB0aGUgYWdncmVnYXRlIGxldmVsLiBUbyBwdXQgdGhlIGN1cnJlbnQgbW9kZWwgb24gdGhlIHNhbWUgYXZlcmFnZSBsZXZlbCBhcyBpbmRpY2F0ZWQsIHdlIG11c3QgY2FsY3VsYXRlIHRoZSByZWJhc2luZyBmYWN0b3INCg0KVGhlIGNvZGUgYmVsb3cgY2FuIHNlZW0gaW50aW1pZGF0aW5nIHNvIHdlIHdpbGwgZ2l2ZSBhIG1vcmUgaW4gZGVwdGggYnJlYWtkb3duIG9mIHNvbWUgb2YgdGhlIG1haW4gZnVuY3Rpb25zIHVzZWQgd2l0aCBkcGx5ci4gSWYgeW91IGFyZSBmYW1pbGlhciB3aXRoIFNBUyB5b3Ugd2lsbCByZWNvZ25pemUgYSBsb3Qgb2YgdGhlc2UgZnVuY3Rpb25zDQoNCg0KPGI+c2VsZWN0PC9iPjogQSBmdW5jdGlvbiB1c2VkIHRvIGNob29zZSB0aGUgdmFyaWFibGVzIHlvdSBuZWVkDQoNCg0KPGI+Z3JvdXBfYnk8L2I+OiBHcm91cGluZyBieSBhbGxvd3MgZm9yIHRoZSB1c2VyIHRvIGdyb3VwIGEgbG9uZyBhIGxpc3Qgb2YgY2hhcmFjdGVyaXN0aWNzLiBGcmVxdWVudGx5IHVzZWQgd2l0aCBlaXRoZXIgc3VtbWFyaXNlIG9yIHByZXZpb3VzbHkgc2VlbiBzbGljZSgxKS4gDQoNCg0KPGI+c3VtbWFyaXNlPC9iPjogQWxsb3dzIHlvdSB0byBjYWxjdWxhdGUgYWdncmVnYXRlZCB2YXJpYWJsZXMgZm9yIGEgZ3JvdXAgc3VjaCBhcyB0aGUgYXZlcmFnZSwgc3VtLCBzdGFuZGFyZCBkZXZpYXRpb24gYW5kIG90aGVycw0KDQoNCjxiPmZpbHRlcjwvYj46IEFsbG93cyB5b3UgdG8gb25seSB0YWtlIG9ic2VydmF0aW9uIHRoYXQgbWF0Y2ggYSBjZXJ0YWluIGNyaXRlcmlhLCB0aHVzIGFsbG93aW5nIHlvdSB0byBzdWJzZXQgeW91ciBkYXRhDQoNCg0KPGI+bGVmdF9qb2luPC9iPjogQWxsb3dzIHlvdSB0byBqb2luIHR3byB0YWJsZXMgdG9nZXRoZXIgam9pbmVkIGJ5IGEgdmFyaWFibGUgKG9mIG11bHRpcGxlIG9uZXMpLCB3aGljaCBhY3RzIGFzIGEgdW5pcXVlIGtleQ0KDQoNCjxiPm11dGF0ZTwvYj46IEFsbG93cyB5b3UgdG8gYWRkIG1vcmUgdmFyaWFibGVzIHRvIHlvdXIgdGFibGVzLiBNdXRhdGUgY3JlYXRlcyBuZXcgdmFyaWFibGVzLCB5b3UgY2FuIGNvbXBhcmUgaXQgd2l0aCB0aGUgZGF0YTsgc2V0OyBzdGVwIGluIFNBUy4gDQoNCg0KV2UnbGwgaW5jbHVkZSBhIG1vcmUgZGV0YWlsZWQgYnJlYWtkb3duIG9mIGNhbGN1bGF0aW5nIHRoZSByZWJhc2UgZmFjdG9yLCBhbmQgd2UnbGwgaW5jbHVkZSB0aGUgZnVsbCBzdGVwIGluIG9uZSBjYWxjdWxhdGlvbiB1bmRlcm5lYXRoIGl0Lg0KDQoNClRoZSBmaXJzdCBzdGVwIGludm9sdmVzIGEgY2FsY3VsYXRpb24gb2YgdGhlIExvc3MgQ29zdCBmb3IgdGhlIHJlc3BlY3RpdmUgdmVoaWNsZXMuIEZvciB0aGF0IHdlIHN1bSBvdXIgbG9zcyBjb3N0IGJ5IHZlaGljbGUgdXNpbmcgdGhlIGdyb3VwX2J5IGZ1bmN0aW9uLg0KDQpgYGB7ciBlY2hvID0gVFJVRX0NClJlYmFzZV9GYWN0b3JfMSA8LSBEQVRBX1Njb3JlZCAlPiUNCiAgZ3JvdXBfYnkoQ2x0X0luc3VyZXJfVHgsDQogICAgICAgICAgIENsdF9BY2NvdW50X05vLCANCiAgICAgICAgICAgUG9sX1BvbGljeV9ObywgDQogICAgICAgICAgIFZlaF9JZF9ObykgJT4lDQogIHN1bW1hcmlzZShMQ19WZWhfVG90YWwgPSBzdW0oTENfVE9UQUwpKQ0KYGBgDQoNCldlIHRoZW4gZ3JvdXAgYnkgb3VyIGNvbXBhbnkgdG8gZmluZCB0aGUgYXZlcmFnZSBsb3NzIGNvc3QgZm9yIGVhY2ggY29tcGFueS4gbmEucm0gaXMgYW4gb3B0aW9uIHdoaWNoIGV4Y2x1ZGUgdGhlIE5BIHZhbHVlcyBmb3IgdGhlIG1lYW4gY2FsY3VsYXRpb24gKG5vdCBmcm9tIHRoZSBkYXRhKS4gSWYgd2UgaGF2ZSBhIE5BIHZhbHVlIGFuZCB3ZSBkb24ndCBhZGQgdGhpcyBvcHRpb24sIG1lYW4gd2lsbCByZXR1cm4gTkEuDQoNCmBgYHtyIGVjaG8gPSBUUlVFfQ0KUmViYXNlX0ZhY3Rvcl8yIDwtIFJlYmFzZV9GYWN0b3JfMSAlPiUNCiAgZ3JvdXBfYnkoQ2x0X0luc3VyZXJfVHgpICU+JQ0KICBzdW1tYXJpc2UoRXhwb3N1cmUgPSBzdW0obigpKSwNCiAgICAgICAgICAgIExDX1ZlaF9Ub3RhbF9BdmcgPSBtZWFuKExDX1ZlaF9Ub3RhbCwgbmEucm0gPSBUUlVFKSkgDQpgYGANCg0KDQpgYGB7cn0NCnByaW50KFJlYmFzZV9GYWN0b3JfMikNCmBgYA0KDQpGaW5hbGx5IHdlIGpvaW4gdGhlc2UgbG9zcyBjb3N0IHZlaGljbGUgYXZlcmFnZXMgdG8gdGhlIGluZGljYXRlZCB0b3RhbCBhbW91bnQgdG8gY2FsY3VsYXRlIHRoZSByZWJhc2luZyBmYWN0b3IuIEJlY2F1c2Ugd2Ugc2hvdWxkIG5vdCBoYXZlIFRER0kgaW4gb3VyIGRhdGEgKG9ubHkgb25lIGV4cG9zdXJlKSwgd2Ugd2lsbCByZW1vdmUgaXQNCg0KYGBge3IgZWNobyA9IFRSVUV9DQpSZWJhc2VfRmFjdG9yXzMgPC0gUmViYXNlX0ZhY3Rvcl8yICU+JQ0KICBsZWZ0X2pvaW4oREFUQV9JbmRpY2F0ZWQpICU+JQ0KICBmaWx0ZXIoQ2x0X0luc3VyZXJfVHggIT0gIlRER0kiKSAlPiUNCiAgbXV0YXRlKFJlYmFzZV9GYWN0b3IgPSBJbmRpY2F0ZWRfVG90YWxfQXZnX0FtL0xDX1ZlaF9Ub3RhbF9BdmcpDQpgYGANCg0KYGBge3J9DQpwcmludChSZWJhc2VfRmFjdG9yXzMpDQpgYGANCg0KDQpBbmQgdGhpcyBjYW4gYWxsIGJlIGNvbWJpbmVkIGludG8gdGhpcyBvbmUgY29kZSBiZWxvdw0KDQpgYGB7ciBlY2hvID0gVFJVRX0NClJlYmFzZV9GYWN0b3IgPC0gREFUQV9TY29yZWQgJT4lDQogIGdyb3VwX2J5KENsdF9JbnN1cmVyX1R4LA0KICAgICAgICAgICBDbHRfQWNjb3VudF9ObywgDQogICAgICAgICAgIFBvbF9Qb2xpY3lfTm8sIA0KICAgICAgICAgICBWZWhfSWRfTm8pICU+JQ0KICBzdW1tYXJpc2UoTENfVmVoX1RvdGFsID0gc3VtKExDX1RPVEFMKSkgJT4lDQogIGdyb3VwX2J5KENsdF9JbnN1cmVyX1R4KSAlPiUNCiAgc3VtbWFyaXNlKEV4cG9zdXJlID0gc3VtKG4oKSksDQogICAgICAgICAgICBMQ19WZWhfVG90YWxfQXZnID0gbWVhbihMQ19WZWhfVG90YWwsIG5hLnJtPVRSVUUpKSAlPiUNCiAgbGVmdF9qb2luKERBVEFfSW5kaWNhdGVkKSAlPiUNCiAgZmlsdGVyKENsdF9JbnN1cmVyX1R4ICE9ICJUREdJIikgJT4lDQogIG11dGF0ZShSZWJhc2VfRmFjdG9yID0gSW5kaWNhdGVkX1RvdGFsX0F2Z19BbS9MQ19WZWhfVG90YWxfQXZnKQ0KYGBgDQoNCg0KYGBge3J9DQpwcmludChSZWJhc2VfRmFjdG9yKQ0KYGBgDQoNCk9uY2UgdGhlIHJlYmFzaW5nIGZhY3RvciBpcyBjYWxjdWxhdGVkLCB3ZSBjYW4gcmViYXNlIHRoZSBzY29yZWQgdG90YWwgbG9zcyBjb3N0IHRvIGhhdmUgdGhlIHNhbWUgYXZlcmFnZSBpbmRpY2F0ZWQgbG9zcyBjb3N0IGZvciBlYWNoIGNvbXBhbnkNCg0KDQpBcHBseSByZWJhc2luZyBmYWN0b3INCmBgYHtyfQ0KREFUQV9TY29yZWQgPC0gREFUQV9TY29yZWQgJT4lDQogIGxlZnRfam9pbihSZWJhc2VfRmFjdG9yKSAlPiUNCiAgbXV0YXRlKExDX1RPVEFMX1JlYmFzZWQgPSBMQ19UT1RBTCAqIFJlYmFzZV9GYWN0b3IpDQpgYGANCg0KDQojIyBQcm9maXRhYmlsaXR5IEFuYWx5c2lzIChBIExlc3NvbiBpbiBEYXRhIE1hbmlwdWxhdGlvbikNCg0KQmVmb3JlIG1vdmluZyBvbiB0byBwbG90dGluZywgbGV0J3MgY29uY2x1ZGUgb3VyIGV4YW1wbGUgd2l0aCBhIHByb2ZpdGFiaWxpdHkgYW5hbHlzaXMuIA0KRmlyc3QgbG9va2luZyBhdCBleHBlY3RlZCBwcm9maXRhYmlsaXR5IG9mIHRoZSBxdW90ZXMgdGhhdCBjbG9zZWQgYnkgY29tcGFueQ0KDQpgYGB7ciB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0NCg0KQUdHLlZFSCA8LSBEQVRBX1Njb3JlZCAlPiUNCiAgZ3JvdXBfYnkoQ2x0X0luc3VyZXJfVHgsDQogICAgICAgICAgIENsdF9BY2NvdW50X05vLCANCiAgICAgICAgICAgUG9sX1BvbGljeV9ObywgDQogICAgICAgICAgIFZlaF9JZF9ObykgJT4lDQogIHN1bW1hcmlzZShMQ19WZWhfVG90YWwgPSBzdW0oTENfVE9UQUwpKQ0KDQpBR0cuSU5TVVJFUiA8LSBEQVRBX1Njb3JlZCAlPiUgDQogIGxlZnRfam9pbihBR0cuVkVIKSAlPiUNCiAgZmlsdGVyKENsb19TYWxlX0luID09IDEgJiBEcmlfVHlwZV9DZCA9PSAnUCcpICU+JSAjIFRvIGhhdmUgdGhlIGRhdGEgYnkgdmVoaWNsZQ0KICBzZWxlY3QoTENfVmVoX1RvdGFsLA0KICAgICAgICAgUHJtX1RybV9WZWhfVG90X0FtLA0KICAgICAgICAgQ2x0X0luc3VyZXJfVHgpICU+JSANCiAgZ3JvdXBfYnkoQ2x0X0luc3VyZXJfVHgpICU+JSANCiAgc3VtbWFyaXNlKFBybV9Ucm1fVmVoX01lYW4gPSBtZWFuKFBybV9Ucm1fVmVoX1RvdF9BbSksDQogICAgICAgICAgICBMUl9WZWhfTWVhbiAgPSBzdW0oTENfVmVoX1RvdGFsLCBuYS5ybSA9IFRSVUUpL3N1bShQcm1fVHJtX1ZlaF9Ub3RfQW0pLA0KICAgICAgICAgICAgWHBvX1ZlaF9OYiAgID0gc3VtKG4oKSkpDQoNCmBgYA0KDQpgYGB7ciB9DQpwcmludChBR0cuSU5TVVJFUikNCmBgYA0KDQpUaGVuIHdlIGNhbiBjYWxjdWxhdGUgdGhlIGV4cGVjdGVkIHByb2ZpdGFibGl0eSBvZiB0aGUgcXVvdGVzIHRoYXQgY2xvc2VkIGJ5IE1hcml0YWwgU3RhdHVzIGFuZCBEcml2ZXIgQWdlDQoNCmBgYHtyIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQ0KQUdHLkRSSV9BR0UgPC0gREFUQV9TY29yZWQgJT4lIA0KICBsZWZ0X2pvaW4oQUdHLlZFSCkgJT4lDQogIGZpbHRlcihDbG9fU2FsZV9JbiA9PSAxICYgRHJpX1R5cGVfQ2QgPT0gJ1AnKSAlPiUgIyBUbyBoYXZlIHRoZSBkYXRhIGJ5IHZlaGljbGUNCiAgc2VsZWN0KExDX1ZlaF9Ub3RhbCwNCiAgICAgICAgIFBybV9Ucm1fVmVoX1RvdF9BbSwNCiAgICAgICAgIERyaV9BZ2VfTmIsDQogICAgICAgICBEcmlfTWFyaXRhbF9TdGF0dXNfSW4pICU+JSANCiAgZ3JvdXBfYnkoRHJpX0FnZV9OYiwNCiAgICAgICAgICAgRHJpX01hcml0YWxfU3RhdHVzX0luKSAlPiUgDQogIHN1bW1hcmlzZShQcm1fVHJtX1ZlaF9NZWFuID0gbWVhbihQcm1fVHJtX1ZlaF9Ub3RfQW0pLA0KICAgICAgICAgICAgTFJfVmVoX01lYW4gID0gc3VtKExDX1ZlaF9Ub3RhbCwgbmEucm0gPSBUUlVFKS9zdW0oUHJtX1RybV9WZWhfVG90X0FtKSwNCiAgICAgICAgICAgIFhwb19WZWhfTmIgICA9IHN1bShuKCkpKQ0KYGBgDQoNCg0KYGBge3J9DQpoZWFkKEFHRy5EUklfQUdFKQ0KYGBgDQoNCkFuZCBmaW5hbGx5LCB3ZSBjYW4gY2FsY3VsYXRlIHRoZSBleHBlY3RlZCBwcm9maXRhYmxpdHkgb2YgdGhlIHF1b3RlcyB0aGF0IGNsb3NlZCBieSB0aGUgR2VuZGVyIG9mIHRoZSBwcmluY2lwYWwgZHJpdmVyIGFuZCBoaXMgbnVtYmVyIG9mIHllYXJzIGxpY2Vuc2VkDQoNCmBgYHtyIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQ0KQUdHLllSU19MSUMgPC0gREFUQV9TY29yZWQgJT4lIA0KICBsZWZ0X2pvaW4oQUdHLlZFSCkgJT4lDQogIGZpbHRlcihDbG9fU2FsZV9JbiA9PSAxICYgRHJpX1R5cGVfQ2QgPT0gJ1AnKSAlPiUgIyBUbyBoYXZlIHRoZSBkYXRhIGJ5IHZlaGljbGUNCiAgc2VsZWN0KExDX1ZlaF9Ub3RhbCwNCiAgICAgICAgIFBybV9Ucm1fVmVoX1RvdF9BbSwNCiAgICAgICAgIERyaV9HZW5kZXJfQ2QsDQogICAgICAgICBEcmlfWXJzX0xpY2Vuc2VkX0FVX05iKSAlPiUgDQogIGdyb3VwX2J5KERyaV9HZW5kZXJfQ2QsDQogICAgICAgICAgIERyaV9ZcnNfTGljZW5zZWRfQVVfTmIpICU+JSANCiAgc3VtbWFyaXNlKFBybV9Ucm1fVmVoX01lYW4gPSBtZWFuKFBybV9Ucm1fVmVoX1RvdF9BbSksDQogICAgICAgICAgICBMUl9WZWhfTWVhbiAgPSBzdW0oTENfVmVoX1RvdGFsLCBuYS5ybSA9IFRSVUUpL3N1bShQcm1fVHJtX1ZlaF9Ub3RfQW0pLA0KICAgICAgICAgICAgWHBvX1ZlaF9OYiAgID0gc3VtKG4oKSkpDQpgYGANCg0KDQpgYGB7cn0NCmhlYWQoQUdHLllSU19MSUMpDQpgYGANCg0KIyBQbG90dGluZyBSZXN1bHRzDQoNCkZpbmFsbHksIHdlIHdpbGwgY292ZXIgUidzIHBsb3R0aW5nIGZ1bmN0aW9uYWxpdHkgYmVmb3JlIG1vdmluZyBvbnRvIG91ciBmdW5jdGlvbnMgbGVzc29uLiBSIGhhcyB0d28gbWFpbiBwYWNrYWdlcyB3aGljaCBvZmZlciBleHRyZW1lbHkgcG93ZXJmdWwgb3B0aW9ucyBmb3IgcGxvdHRpbmc6IGdncGxvdDIgYW5kIHBsb3RseS4gRWFjaCBoYXZlIHRoZWlyIG93biBzdHJlbmd0aHMgYW5kIHdlYWtuZXNzZXMsIGJ1dCB1bHRpbWF0ZWx5IHRoZXkncmUgYm90aCBwb3dlcmZ1bCBlbm91Z2ggdGhhdCB5b3VyIGRlY2lzaW9uIGNhbiBjb21lIGRvd24gdG8gcGVyc29uYWwgcHJlZmVyZW5jZS4gSW4gdGhpcyBsZXNzb24sIHdlJ2xsIHVzZSBwbG90bHksIGJ1dCB0aGUgcmVhZGVyIGlzIGVuY291cmFnZWQgdG8gbG9vayBhdCBnZ3Bsb3QyIGlmIHRoZXkgYXJlIGludGVyZXN0ZWQuIA0KDQpKdXN0IGFzIGFuIGZ5aSwgZ2dwbG90MiBkb2VzIG5vdCBhbGxvdyB0byBoYXZlIDIgeS1heGlzLiBJbiB0aGF0IGNhc2UsIHRoZSBwYWNrYWdlIHBsb3RseSBpcyBwcmVmZXJyZWQNCg0KDQojIyBFeGFtcGxlOiBWaXN1YWxpemF0aW9uIChwcm9maXRhYmlsaXR5IGFuYWx5c2lzKQ0KDQo8Yj5Mb3NzIFJhdGlvIGJ5IEluc3VyZXI8L2I+DQoNClRoZSBnb2FsIG9mIHRoaXMgZ3JhcGggaXMgdG8gYmUgYWJsZSB0byBoYXZlIGJvdGggYSBsaW5lIGdyYXBoIGFuZCBhIGJhciBncmFwaCB3aXRoaW4gdGhlIHNhbWUgcGxvdC4gVGhpcyBhbGxvd3MgdXNlciB0byB2aXN1YWxpemUgbXVsdGlwbGUgYXNwZWN0cyBvZiB0aGUgZGF0YSBhdCB0aGUgc2FtZSB0aW1lLiBJbiB0aGlzIGNhc2UgdGhlIGJhciBncmFwaCB3aWxsIHJlcHJlc2VudCBleHBvc3VyZSBmb3IgZWFjaCBpbnN1cmVyIHdoaWxlIHRoZSBsaW5lIGdyYXBoIG92ZXJsYXlpbmcgaXQgd2lsbCByZXByZXNlbnQgdGhlIGF2ZXJhZ2UgbG9zcyByYXRpby4NCg0KRm9yIGJ1aWxkaW5nIHBsb3RzIHdlIGNhbiB1c2UgYSBzaW1pbGFyIHN0ZXAgYnkgc3RlcCBhcHByb2FjaCBhcyBkcGx5cg0KDQpgYGB7ciBlY2hvID0gVFJVRX0NClBfTFJfSW5zdXJlciA8LSBwbG90X2x5KGRhdGEgPSBBR0cuSU5TVVJFUikgJT4lDQogIGFkZF9saW5lcygNCiAgICB4ID0gfkNsdF9JbnN1cmVyX1R4LA0KICAgIHkgPSB+TFJfVmVoX01lYW4sDQogICAgbmFtZSA9ICJMb3NzIFJhdGlvIiwNCiAgICB5YXhpcyA9ICJ5Ig0KICApDQoNCmBgYA0KDQpgYGB7cn0NClBfTFJfSW5zdXJlcg0KYGBgIA0KDQpBbGxvd3MgbW9yZSBhbmQgbW9yZSBncmFwaGljIGl0ZW1zIHRvIGJlIGFkZGVkIG9uLiBBbGwgdGhhdCBuZWVkcyB0byBiZSBkb25lIGlzIHRvIGtub3cgdGhlIGNvcnJlY3QgZ3JhbW1hciBvZiB0aGUgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2UuDQoNCmBgYHtyIGVjaG8gPSBUUlVFfQ0KUF9MUl9JbnN1cmVyMiA8LSBwbG90X2x5KGRhdGEgPSBBR0cuSU5TVVJFUikgJT4lDQogIGFkZF9saW5lcyh4ID0gfkNsdF9JbnN1cmVyX1R4LCB5ID0gfkxSX1ZlaF9NZWFuLA0KICAgICAgICAgICAgbmFtZSA9ICJMb3NzIFJhdGlvIiwNCiAgICAgICAgICAgIHlheGlzID0gInkiDQogICkgJT4lDQogIGFkZF9iYXJzKHggPSB+Q2x0X0luc3VyZXJfVHgsIHkgPSB+WHBvX1ZlaF9OYiwgDQogICAgICAgICAgIG5hbWUgPSAiRXhwb3N1cmUiLA0KICAgICAgICAgICB5YXhpcyA9ICJ5MiINCiAgKSAlPiUNCiAgbGF5b3V0KA0KICAgIHRpdGxlID0gIkxvc3MgUmF0aW8gYnkgSW5zdXJlciIsDQogICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIkluc3VyZXIiKSwNCiAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiTG9zcyBSYXRpbyIsIHNpZGUgPSAibGVmdCIsIG92ZXJsYXlpbmcgPSAieTIiKSwNCiAgICB5YXhpczIgPSBsaXN0KHRpdGxlID0gIkV4cG9zdXJlIiwgc2lkZSA9ICJyaWdodCIpDQogICkNCg0KYGBgDQoNCmBgYHtyIGVjaG8gPSBGQUxTRX0NCiMgVmlldyB0aGUgY2hhcnQNClBfTFJfSW5zdXJlcjINCmBgYA0KDQo8Yj5SZXNjYWxlIHktYXhpczwvYj4NCmBgYHtyfQ0KUF9MUl9JbnN1cmVyMyA8LSBQX0xSX0luc3VyZXIyICU+JQ0KICBsYXlvdXQoDQogICAgeWF4aXMgPSBsaXN0KA0KICAgICAgcmFuZ2UgPSBjKDAsIDIpDQogICAgKQ0KICApDQpgYGANCg0KYGBge3J9DQpQX0xSX0luc3VyZXIzDQpgYGANCg0KPGI+TWFyaXRhbCB4IERyaXZlciBBZ2U8L2I+DQoNCmBgYHtyIGVjaG8gPSBUUlVFfQ0KQUdHLkRSSV9BR0UgPC0gdW5ncm91cChBR0cuRFJJX0FHRSkNCg0KDQpQX0xSX01hcml0YWxfQWdlIDwtIHBsb3RfbHkoZGF0YSA9IEFHRy5EUklfQUdFLCBjb2xvciA9fkRyaV9NYXJpdGFsX1N0YXR1c19JbiwgY29sb3JzID0gYygicmVkIiwiZGFya2dyZWVuIikpICU+JQ0KICBhZGRfYmFycyh4PX5EcmlfQWdlX05iLCB5PX5YcG9fVmVoX05iLCB5YXhpcz0ieTIiLCBvcGFjaXR5ID0gLjUpJT4lDQogIGFkZF9saW5lcyh4PX5EcmlfQWdlX05iLCB5PX5MUl9WZWhfTWVhbikgJT4lDQogIGxheW91dCgNCiAgICBiYXJtb2RlPSJzdGFjayIsDQogICAgdGl0bGUgPSAiTG9zcyBSYXRpbyBieSBEcml2ZXIgQWdlIGFuZCBNYXJpdGFsIFN0YXR1cyIsDQogICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIkRyaXZlciBBZ2UiLCByYW5nZSA9IGMoMTYsIDcwKSksDQogICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIkxvc3MgUmF0aW8iLCBzaWRlID0gImxlZnQiLCBvdmVybGF5aW5nID0gInkyIiksDQogICAgeWF4aXMyID0gbGlzdCh0aXRsZSA9ICJFeHBvc3VyZSIsIHNpZGUgPSAicmlnaHQiKQ0KICApDQpgYGANCg0KYGBge3J9DQpQX0xSX01hcml0YWxfQWdlDQpgYGANCg0KDQo8Yj5HZW5kZXIgeCBZcnMgTGljZW5zZWQ8L2I+DQoNCmBgYHtyIGVjaG8gPSBUUlVFfQ0KUF9MUl9HZW5kZXJfWUwgPC0gcGxvdF9seShkYXRhID0gQUdHLllSU19MSUMsIGNvbG9yID1+RHJpX0dlbmRlcl9DZCwgY29sb3JzID0gYygicmVkIiwiZGFya2dyZWVuIikpICU+JQ0KICBhZGRfYmFycyh4PX5EcmlfWXJzX0xpY2Vuc2VkX0FVX05iLCB5PX5YcG9fVmVoX05iLCB5YXhpcz0ieTIiKSU+JQ0KICBhZGRfbGluZXMoeD1+RHJpX1lyc19MaWNlbnNlZF9BVV9OYiwgeT1+TFJfVmVoX01lYW4pICU+JQ0KICBsYXlvdXQoDQogICAgYmFybW9kZT0iZ3JvdXAiLA0KICAgIHRpdGxlID0gIkxvc3MgUmF0aW8gYnkgR2VuZGVyIGFuZCBZZWFycyBMaWNlbnNlZCIsDQogICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIlllYXJzIExpY2Vuc2VkIiwgcmFuZ2UgPSBjKC0xLCA2MCkpLA0KICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJMb3NzIFJhdGlvIiwgc2lkZSA9ICJsZWZ0Iiwgb3ZlcmxheWluZyA9ICJ5MiIpLA0KICAgIHlheGlzMiA9IGxpc3QodGl0bGUgPSAiRXhwb3N1cmUiLCBzaWRlID0gInJpZ2h0IikNCiAgKQ0KYGBgDQoNCmBgYHtyfQ0KUF9MUl9HZW5kZXJfWUwNCmBgYA0KDQoNCldlIGNhbiBjbGVhcmx5IHNlZSB0aGF0IGdyYXBocyBvZiB0aGUgc2FtZSB0eXBlIGhhdmUgYSB2ZXJ5IHNpbWlsYXIgY29kZSBzdHJ1Y3R1cmUgYWxsb3dpbmcgdXNlcnMgdG8gY3JlYXRlIG5ldyBncmFwaHMgZWFzaWx5DQoNCg0KV2UgY2FuIG5vdyBzYXZlIHRoZXNlIGdyYXBocyB0byBodG1sIGZpbGVzIHRoYXQgY2FuIGJlIHVzZWQgZm9yIGxhdGVyIGFuYWx5c2lzLiBEbyBub3QgZm9yZ2V0IHRvIGNoYW5nZSB0aGUgcGF0aCBmb3IgeW91ciBvd24gZGlyZWN0b3J5Lg0KYGBge3J9DQpwYXRoIDwtIlM6L1ByaWNpbmcvSW5ub3ZhdGlvbi9SIFJlc3NvdXJjZXMvUiAtIFRyYWluaW5nIDIuMC9UcmFpbmluZy8iDQpzYXZlV2lkZ2V0KFBfTFJfSW5zdXJlcixmaWxlLnBhdGgobm9ybWFsaXplUGF0aChkaXJuYW1lKHBhc3RlMChwYXRoLCAiQ2x0X0luc3VyZXJfVHgiLCAiLmh0bWwiKSkpLGJhc2VuYW1lKHBhc3RlMChwYXRoLCAiQ2x0X0luc3VyZXJfVHgiLCAiLmh0bWwiKSkpKQ0KDQpzYXZlV2lkZ2V0KFBfTFJfR2VuZGVyX1lMLGZpbGUucGF0aChub3JtYWxpemVQYXRoKGRpcm5hbWUocGFzdGUwKHBhdGgsICJHZW5kZXJfeF9ZTCIsICIuaHRtbCIpKSksYmFzZW5hbWUocGFzdGUwKHBhdGgsICJHZW5kZXJfeF9ZTCIsICIuaHRtbCIpKSkpDQoNCmBgYA0KDQo=